package repository

import (
	"fmt"
	gamedb "game/game-db"
	"game/game-db/orm"
	heap "game/readygo/quickstart/repository/heap"
	"reflect"
	"sync"

	"go.uber.org/zap"
)

type cacheRepository[T orm.IDbEntity] struct {
	dao            gamedb.IDao[T]
	cache          *heap.HeapCache[T]
	onlyPrimaryKey bool
	lock           sync.Mutex
	firstKeyName   string
	relationFields []string
}

type CacheInfo struct {
	RelationFields []string //存取关系
	AliveTime      int64    //-1表示不会过期，0表示默认一小时
}

// 缓存仓库实体
type ICacheRepositoryEntity interface {
	orm.IDefaultDbEntity
	CacheInfo() *CacheInfo
}

func NewCacheRepository[T ICacheRepositoryEntity](dao gamedb.IDao[T], entityTemplate T) IRepository[T] {
	var repository cacheRepository[T]
	repository.dao = dao
	cacheInfo := entityTemplate.CacheInfo()
	aliveTime := cacheInfo.AliveTime
	if aliveTime == 0 {
		//1个小时缓存时间
		aliveTime = 1000 * 60 * 60
	} else if aliveTime == -1 {
		aliveTime = 0
	}
	var relationFields []string
	if cacheInfo.RelationFields == nil || len(cacheInfo.RelationFields) <= 0 {
		primaryFieldName := dao.EntityMeta().FieldNameByColumnName(dao.EntityMeta().PrimaryKeyName())
		relationFields = make([]string, 0)
		relationFields = append(relationFields, primaryFieldName)
	} else {
		relationFields = cacheInfo.RelationFields
	}
	repository.cache = heap.CreateHeapCache[T](relationFields, aliveTime)
	repository.onlyPrimaryKey = len(relationFields) == 1
	repository.firstKeyName = relationFields[0]
	repository.relationFields = relationFields
	return &repository
}

func (t *cacheRepository[T]) loadEntities(firstKey interface{}) {
	t.lock.Lock()
	// fmt.Println("Lock", firstKey)
	defer t.lock.Unlock()

	if t.cache.Has(firstKey) {
		return
	}
	columName := t.dao.EntityMeta().ColumnNameByFieldName(t.firstKeyName)
	where := fmt.Sprintf("WHERE %s=?", columName)
	entities := t.dao.QueryWhere(where, firstKey)
	if len(entities) <= 0 {
		t.cache.CreateEmptyRoot(firstKey)
	} else {
		t.cache.Add(entities...)
	}
	zap.L().Info(fmt.Sprintf("load %s data with %s=%v, result size %d", t.dao.TableName(), columName, firstKey, len(entities)))
	// fmt.Println("Unlock", firstKey)
}

func (t *cacheRepository[T]) ListByKeys(keys ...interface{}) []T {
	if keys == nil || len(keys) <= 0 {
		panic("ListByKeys need keys not empty")
	}
	var keyOptions keyOptions
	for i := 0; i < len(keys) && i < len(t.relationFields); i++ {
		keyOptions.Put(t.relationFields[i], keys[i])
	}
	keyValues := keyOptions.KeyValues()
	entities := t.cache.GetList(keyValues...)
	if entities == nil {
		if t.cache.Has(keyValues[0]) {
			return make([]T, 0)
		} else {
			t.loadEntities(keyValues[0])
			entities = t.cache.GetList(keyValues...)
		}
	}
	//else get data from heap cache
	return entities
}

func (t *cacheRepository[T]) GetByKeys(keys ...interface{}) (T, bool) {
	if keys == nil || len(keys) <= 0 {
		panic("GetByKeys need keys not empty")
	}
	var keyOptions keyOptions
	for i := 0; i < len(keys) && i < len(t.relationFields); i++ {
		keyOptions.Put(t.relationFields[i], keys[i])
	}
	keyValues := keyOptions.KeyValues()
	entity, ok := t.cache.Get(keyValues...)
	if !ok {
		if t.cache.Has(keyValues[0]) {
			return entity, false
		} else {
			t.loadEntities(keyValues[0])
			entity, ok = t.cache.Get(keyValues...)
		}
	}
	//else get data from heap cache
	return entity, ok
}

func (t *cacheRepository[T]) Insert(entity T) error {
	err := t.dao.Insert(entity)
	if err == nil {
		t.cache.Add(entity)
	}
	return err
}

func (t *cacheRepository[T]) InsertAll(entities []T) {
	t.dao.InsertAll(entities)
	t.cache.Add(entities...)
}

func (t *cacheRepository[T]) Update(entity T) {
	t.dao.Update(entity)
	//不需要更新缓存，因为内存地址是一致的
}

func (t *cacheRepository[T]) UpdateAll(entities []T) {
	t.dao.UpdateAll(entities)
	//不需要更新缓存，因为内存地址是一致的
}

func (t *cacheRepository[T]) Delete(entity T) {
	t.dao.Delete(entity)
	t.cache.Delete(entity)
}

func (t *cacheRepository[T]) DeleteAll(entities []T) {
	t.dao.DeleteAll(entities)
	t.cache.Delete(entities...)
}

func (t *cacheRepository[T]) Scan() {
	result := t.cache.Sprint()
	fmt.Println(result)
}

// 从仓库中加载所有数据
func (t *cacheRepository[T]) LoadAll() []T {
	allInDb := t.dao.QueryWhere("")
	results := make([]T, 0)
	for _, entityInDb := range allInDb {
		rValue := reflect.ValueOf(entityInDb).Elem()
		var keyOptions keyOptions
		for i := 0; i < len(t.relationFields); i++ {
			rField := rValue.FieldByName(t.relationFields[i])
			keyOptions.Put(t.relationFields[i], rField.Interface())
		}
		keyValues := keyOptions.KeyValues()
		entityInCache, ok := t.cache.Get(keyValues...)
		if ok {
			results = append(results, entityInCache)
		} else {
			results = append(results, entityInDb)
			t.cache.Add(entityInDb)
		}
	}
	zap.L().Info(fmt.Sprintf("Load all data from %s, data size %d", t.dao.TableName(), len(results)))
	return results
}
